#pragma once

#include <algorithm>
#include <functional>
#include <numeric>
#include <vector>

#include "TermVisitor.hpp"
#include "to_string.hpp"

namespace terms {
	enum class TermListType {
		ORDERED,
		SORTED,
		UNIQUE
	};

	/**
	 * \brief A generic implementation of an ordered list of terms
	 *
	 * This is a generic list for storing a fixed number of ordered
	 * terms. The list is immutable after construction.
	 *
	 * Term lists can be one of three possible types:
	 * ORDERED, SORTED, or UNIQUE
	 *
	 * + An ORDERED term list keeps the terms in the order
	 * they were inserted initially. Two terms lists are equal
	 * only if their terms appear in exactly the same order.
	 *
	 * + A SORTED term list does a stable sort on the input terms
	 * and represents them in order. Duplicate terms are possible.
	 *
	 * + A UNIQUE term list removes duplicate terms
	 *
	 * Term list access to subterms is intended to be as fast as
	 * possible without sacrificing to use a common base class
	 * for term list's of different sizes (as with std::array<<>)
	 *
	 * \tparam Term parameter refers to the type of the terms 
	 * that can be stored inside the term list.
	 *
	 * \tparam BaseTerm When deriving from this class, this template argument 
	 * should refer to the base term of term system (see \ref TermSystem).
	 * The TermList will be a subclass of BaseTerm.
	 **/
	template <typename BaseTerm, typename Term=BaseTerm>
	class TermList
		: protected std::vector<const Term *>
	{
	private:
		TermList(const TermList &)
		{
		}

	private:
		TermList(TermList &&)
		{
		}

	public:
		/**
		 * Create a new term list by copying term pointers
		 * from the specified \p container.
		 *
		 * \tparam The type of the input container
		 * \param type The ordering type of the term list to create.
		 **/
		template <typename Container>
		TermList(TermListType type, const Container &container)
		{
			this->insert(end(), container.begin(), container.end());
			fix_list(type);
		}

	public:
		/**
		 * Construct a new term list from an iterator range.
		 *
		 * \tparam Iter The type of the input iterators
		 * \param type The ordering type of the term list to create.
		 * \param b The start of the iterator range.
		 * \param e The past-the-end iterator for the iterator range.
		 **/
		template <typename Iter>
		TermList(TermListType type, const Iter &b, const Iter &e)
		{
			this->insert(end(), b, e);
			fix_list(type);
		}

	public:
		using std::vector<const Term *>::begin;
		using std::vector<const Term *>::end;
		using std::vector<const Term *>::rbegin;
		using std::vector<const Term *>::rend;
		using std::vector<const Term *>::cbegin;
		using std::vector<const Term *>::cend;
		using std::vector<const Term *>::crbegin;
		using std::vector<const Term *>::crend;

		using typename std::vector<const Term *>::value_type;
		using typename std::vector<const Term *>::const_reference;
		using typename std::vector<const Term *>::const_pointer;
		using typename std::vector<const Term *>::difference_type;
		using typename std::vector<const Term *>::size_type;

		using typename std::vector<const Term *>::const_reverse_iterator;
		using typename std::vector<const Term *>::const_iterator;
		using typename std::vector<const Term *>::iterator;
		using typename std::vector<const Term *>::reverse_iterator;

		using std::vector<const Term *>::size;
		using std::vector<const Term *>::empty;
		using std::vector<const Term *>::operator[];
		using std::vector<const Term *>::at;
		using std::vector<const Term *>::front;
		using std::vector<const Term *>::back;

	public:
		virtual std::size_t
		hash() const
		{
			return std::accumulate(cbegin(), cend(), std::size_t(0), [] (std::size_t sum, const Term *term) {
				return sum + term->hash();
			});	
		}

	public:
		/**
		 * \brief Convert the term list to a string reprsentation by 
		 *
		 * This converts the stored terms to strings (using \ref terms::ptr_to_string).
		 * In the output, individual terms are separated by \p sep.
		 *
		 * \param sep The separator to use in the output string between
		 * individual terms
		 * \returns A string representation of the current term list.
		 **/
		virtual std::string
		to_string(const std::string sep) const
		{
			terms::join<const Term *> joiner;
			return joiner(sep, this->begin(), this->end(), terms::ptr_to_string);
		}

	public:	
		/**
		 * \brief Convert the term list into a string representation
		 * with a standard separator.
		 **/
		virtual std::string
		to_string() const
		{
			return to_string(", ");
		}

//	public:	
//		/**
//		 * \brief Compare two term lists for equal content.
//		 *
//		 * \returns true iff the two term lists contain the same tersms in
//		 * the same order. Terms are compared using object equality (==
//		 * operator), not pointer equality.
//		 **/
//		virtual bool 
//		operator==(const BaseTerm &tg) const
//		{
//			if (dynamic_cast<const BaseTerm *>(this) == &tg) {
//				return true;
//			} else {	
//				const TermList<BaseTerm, Term> *other = dynamic_cast<const TermList<BaseTerm, Term> *>(&tg);
//				if (other == nullptr) {
//					return false;
//				} else {	
//					return std::equal(cbegin(), cend(), other->cbegin(), [](const Term *t0, const Term *t1) {
//						return (t0 == t1) || (*t0 == *t1);
//					});
//				}
//			}
//		}
//	
//	public:
//		/**
//		 * \brief Lexicographical ordering of term lists.
//		 *
//		 * \p tg The list to compare against.
//		 * \returns true iff the current list is lexicographically less
//		 * than \p tg.
//		 **/
//		virtual bool 
//		operator<(const BaseTerm &tg) const
//		{
//			if (this->get_type_order() < tg.get_type_order()) {
//				return true;
//			} else if (this->get_type_order() == tg.get_type_order()) {
//				const TermList<BaseTerm, Term> &other = dynamic_cast<const TermList<BaseTerm, Term> &>(tg);
//				auto first_difference = std::mismatch(cbegin(), cend(), other.cbegin(), [] (const Term *t0, const Term *t1) {
//					return *t0 == *t1;
//				});
//
//				// XXX - simplify this
//				bool first_at_end = (std::get<0>(first_difference) == cend());
//				bool second_at_end = (std::get<1>(first_difference) == other.cend());
//				if (first_at_end && second_at_end) {
//					return true;
//				} else if (first_at_end) {
//					return true;
//				} else if (second_at_end) {
//					return false;
//				} else {
//					return *std::get<0>(first_difference) < *std::get<1>(first_difference);
//				}	
//			} else {
//				return false;
//			}
//		}
//
//	public:
//		virtual typename BaseTerm::TypeOrder get_type_order() const = 0;

	private:
		void
		fix_list(TermListType type)
		{
			switch (type) {
				case TermListType::ORDERED:
					break;

				case TermListType::SORTED:
					sort(begin(), end());
					break;

				case TermListType::UNIQUE:	
					sort(begin(), end());
					unique(begin(), end());
					break;
			}
		}

		// XXX - maybe add support for custom comparer
	};


	/**
	 * Convenience class for \ref TermList 
	 * with a fixed TermType.
	 *
	 * Useful when creating term lists using C++11 initializer lists.
	 **/
	template <TermListType list_type, typename BaseTerm, typename Term=BaseTerm>
	class TypedTermList
		: public TermList<BaseTerm, Term>
	{
	public:	
		template <typename Iter>
		TypedTermList(const Iter &begin, const Iter &end)
			: TermList<BaseTerm, Term>(list_type, begin, end)
		{
		}


	public:	
		template <typename Container>
		TypedTermList(const Container &container)
			: TermList<BaseTerm, Term>(list_type, container)
		{
		}
	};
}

